package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskRefreshService;
import com.heima.schedule.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;

@Slf4j
@Service
public class TaskServiceImpl implements TaskService {

    @Override
    public long addTask(Task task) {

        //1.任务添加到DB任务表中
        boolean flag = addTaskToDB(task);

        //2.任务添加到Cache队列中
        if(flag){
            addTaskToCache(task);
        }

        return task.getTaskId();
    }

    @Autowired
    private CacheService cacheService;

    /**
     * 任务添加到Cache队列中
     *
     * RedisKey设计规范（保证唯一）：
     * 1. 业务前缀
     * 2. 业务标识
     * 3. 冒号拼接
     *
     * 举例说明：
     * 1. 存储一个学生详情，RedisKey设计可以参考： student:info:123
     * 2. 存储一个敏感词列表，RedisKey设计可以参考： sensitive:word:list
     * @param task
     */
    private void addTaskToCache(Task task) {
        //1.获取任务执行时间和系统当前时间
        long executeTime = task.getExecuteTime();//任务执行时间
        long currentTime = DateTime.now().getMillis();//系统当前时间

        //2.比较时间
        //2.1 任务执行时间未到（任务执行时间>系统当前时间），则将任务添加到未来队列ZSet中
        if(executeTime>currentTime){
            //拼接RedisKey
            String redisKey = ScheduleConstants.FUTURE + task.getTaskType() + ":" + task.getPriority();

            //将任务转为JSON存入未来队列ZSet中
            cacheService.zAdd(redisKey, JSON.toJSONString(task), executeTime);

            log.info("[添加任务]添加到Cache-未来队列-结果成功");
        } else {
            //2.2 任务执行时间已到（任务执行时间<=系统当前时间），则将任务添加到当前队列List中

            //拼接RedisKey
            String redisKey = ScheduleConstants.TOPIC + task.getTaskType() + ":" + task.getPriority();

            //将任务转为JSON存入当前队列List中
            cacheService.lLeftPush(redisKey, JSON.toJSONString(task));

            log.info("[添加任务]添加到Cache-当前队列-结果成功");
        }


    }

    @Autowired
    private TaskinfoMapper taskinfoMapper;

    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;

    /**
     * 任务添加到DB任务表中
     * @param task
     * @return
     */
    private boolean addTaskToDB(Task task) {
        try {
            //1.保存task_info数据
            Taskinfo taskinfo = new Taskinfo();
            BeanUtils.copyProperties(task, taskinfo);
            taskinfo.setExecuteTime(new Date(task.getExecuteTime()));
            taskinfoMapper.insert(taskinfo);

            //2.保存task_info_logs数据
            TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
            BeanUtils.copyProperties(taskinfo,taskinfoLogs);
            taskinfoLogs.setVersion(1); //乐观锁版本号
            taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);//初始化状态0
            taskinfoLogsMapper.insert(taskinfoLogs);

            //3.设置task的taskId属性值
            task.setTaskId(taskinfo.getTaskId());

            log.info("[添加任务]添加到DB结果成功");

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Autowired
    private TaskRefreshService taskRefreshService;

    @Scheduled(cron = "*/1 * * * * ?")
    public void taskRefresh(){
        //获取锁
        String lock = cacheService.tryLock("task:refresh", 1);

        //获取锁非空，标识拥有业务执行资格
        if(StringUtils.isNotBlank(lock)){
            log.info("获取到锁");
            //1. 获取全部未来队列KEY的集合
            Set<String> futureKeySet = cacheService.scan(ScheduleConstants.FUTURE + "*");

            if(!futureKeySet.isEmpty()){
                //2. 异步调用任务刷新
                taskRefreshService.refreshTask(futureKeySet);
            }
        } else {
            log.info("未获取到锁");
        }
    }

    /**
     * 同步任务
     */
    //@PostConstruct
    public void reloadTask(){
        //1. 删除Redis未来队列数据
        Set<String> futureKeySet = cacheService.scan(ScheduleConstants.FUTURE + "*");
        if(!futureKeySet.isEmpty()){
            cacheService.delete(futureKeySet);
        }

        //2. 删除Redis当前队列数据
        Set<String> topicKeySet = cacheService.scan(ScheduleConstants.TOPIC + "*");
        if(!topicKeySet.isEmpty()){
            cacheService.delete(topicKeySet);
        }

        //3. 查询任务日志表中状态正常的任务列表  select * from task_info_logs where status=0
        List<TaskinfoLogs> taskinfoLogsList = taskinfoLogsMapper.selectList(Wrappers.<TaskinfoLogs>lambdaQuery()
                .eq(TaskinfoLogs::getStatus, ScheduleConstants.SCHEDULED));

        //4. 将每一个TaskInfoLogs转为Task添加到缓存队列
        if(taskinfoLogsList!=null && taskinfoLogsList.size()>0){
            for (TaskinfoLogs taskinfoLogs : taskinfoLogsList) {
                Task task = new Task();
                BeanUtils.copyProperties(taskinfoLogs,task);
                task.setExecuteTime(taskinfoLogs.getExecuteTime().getTime());

                this.addTaskToCache(task);
            }
        }
    }


    /**
     * 任务拉取
     * @param type  任务类型
     * @param priority 任务优先级
     * @return
     */
    @Override
    public Task poll(int type, int priority) {

        //1.拼接当前队列RedisKey
        String topicRedisKey = ScheduleConstants.TOPIC + type + ":" + priority;

        //2.拉取任务得到JSON字符串
        String taskJSON = cacheService.lRightPop(topicRedisKey);

        if(StringUtils.isNotBlank(taskJSON)){
            //3.将JSON转为Task对象
            Task task = JSON.parseObject(taskJSON, Task.class);

            //4.修改任务日志表的任务为已执行状态  update task_info_logs set status=1,version=version+1 where task_id=? and version=1
            TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
            taskinfoLogs.setTaskId(task.getTaskId()); //更新条件
            taskinfoLogs.setVersion(1);//更新条件
            taskinfoLogs.setStatus(ScheduleConstants.EXECUTED);//更新的数据

            taskinfoLogsMapper.updateById(taskinfoLogs);


            //5.删除任务主表数据
            taskinfoMapper.deleteById(task.getTaskId());

            return task;
        }
        return null;
    }
}
